package com.tool.emulator.common;

import com.tool.emulator.apple2.runtime.EmuState;

import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Emulator.java @author tl 2011-6-17
 */
public class Emulator {
	private static final long NSPS = TimeUnit.SECONDS.toNanos(1);
	final ArrayList<Sync> syncList = new ArrayList<Sync>();
	final ArrayList<DeviceEntry> deviceList = new ArrayList<DeviceEntry>();
	final Clock clock = new Clock();
	final ReentrantLock lock = new ReentrantLock();
	final Condition syncCond = lock.newCondition();
	final Condition manageCond = lock.newCondition();
	volatile Sync[] syncs = new Sync[0];
	volatile DeviceEntry[] devices = new DeviceEntry[0];
	volatile long timestamp = System.nanoTime();
	volatile long paused_counter = 0;
	volatile boolean need_pause;
	volatile boolean shutdown = false;
	private EmuState emuState = new EmuState();

	public Emulator() {
	}

	private static long max_div(long a, long b) {
		for (; ; ) {
			long c = a % b;
			if (c == 0) return b;
			a = b;
			b = c;
		}
	}

	private static long fix_freq(long freq) {
		if (freq <= 0) return 1;
		long f1 = 1L;
		for (long v = freq; v >= 100000L; v /= 10L, f1 *= 10L) ;
		return (freq + f1 / 2) / f1 * f1;
	}

	private static VirtualDevice getDevice(VirtualDevice[] ar, String name) {
		if (name == null) return null;
		for (VirtualDevice d : ar) {
			if (d.getName().equalsIgnoreCase(name))
				return d;
		}
		return null;
	}

	public synchronized void start() throws Exception {
		this.clock.start();
		this.startDevices();
	}

	private void startDevices() throws Exception {
		for (DeviceEntry device : this.devices)
			device.init();
		for (DeviceEntry device : this.devices)
			device.start();
	}

	public synchronized void restart() throws Exception {
		this.shutdown();
		this.shutdown = false;
		this.startDevices();
	}

	public VirtualDevice[] getDevices() {
		DeviceEntry[] ar = this.devices;
		VirtualDevice[] ar1 = new VirtualDevice[ar.length];
		for (int i = 0; i < ar.length; i++)
			ar1[i] = ar[i].device;
		return ar1;
	}

	public synchronized void addDevice(VirtualDevice device) {
		DeviceEntry ent;
		if (device instanceof AutoDevice) {
			Sync sync = new Sync(device);
			this.syncList.add(new Sync(device));
			this.syncs = this.syncList.toArray(new Sync[this.syncList.size()]);
			ent = sync;
		} else
			ent = new DeviceEntry(device);
		this.deviceList.add(ent);
		this.devices = this.deviceList.toArray(new DeviceEntry[this.deviceList.size()]);
	}

	private void clockProc() throws Exception {
		for (; ; ) {
			long currentTime = System.nanoTime();
			lock.lock();
			try {
				this.timestamp = currentTime;
				this.syncCond.signalAll();
			} finally {
				lock.unlock();
			}
			Thread.sleep(1);
		}
	}

	public boolean isPaused() {
		return this.need_pause;
	}

	private boolean isAllPaused() {
		for (Sync sync : this.syncList)
			if (!sync.isPaused()) return false;
		return true;
	}

	public void pause() throws Exception {
		pause(false);
	}

	public void pause(boolean wait) throws Exception {
		lock.lock();
		try {
			if (!this.need_pause) {
				this.need_pause = true;
			}
			while (wait && !shutdown && !this.isAllPaused())
				this.manageCond.await();
		} finally {
			lock.unlock();
		}
	}

	public void resume() throws Exception {
		lock.lock();
		try {
			if (!this.need_pause) return;
			this.paused_counter++;
			this.need_pause = false;
			this.syncCond.signalAll();
		} finally {
			lock.unlock();
		}
	}

	long sync_timestamp(Sync sync, long last) throws Exception {
		if (this.timestamp > last && !this.need_pause)
			return this.timestamp;
		lock.lock();
		try {
			if (!need_pause && !shutdown && this.timestamp <= last) {
				this.syncCond.await();
			}
			if (this.need_pause) {
				sync.paused = true;
				this.manageCond.signalAll();
				try {
					while (!shutdown && this.need_pause) this.syncCond.await();
				} finally {
					sync.paused = false;
				}
			}

		} finally {
			lock.unlock();
		}
		return this.timestamp;
	}

	public void shutdown() throws Exception {
		this.pause();
		this.shutdown = true;
		this.resume();
		lock.lock();
		try {
			this.syncCond.signalAll();
			this.manageCond.signalAll();
		} finally {
			lock.unlock();
		}
		for (DeviceEntry device : this.devices) {
			try {
				device.exit();
			} catch (Exception ex) {
				ex.printStackTrace();
			}

		}
	}

	public VirtualDevice getDevice(String name) {
		return getDevice(this.getDevices(), name);
	}

	//读取保存的快照
	public void loadState(InputStream in) throws Exception {
		boolean paused = this.isPaused();
		if (!paused)
			this.pause(true);
		try {

			//HashMap<String, HashMap<Object, Object>> states = (HashMap<String, HashMap<Object, Object>>)
			//		new ObjectInputStream(new BufferedInputStream(in)).readObject();
			this.emuState.clear();

			for (VirtualDevice device : this.getDevices()) {

				try {
					synchronized (device) {
						device.deviceLoadState(emuState);
					}
				} catch (Exception ex) {
					System.err.println("device " + device.getName() + " load state error");
					ex.printStackTrace();
				}
			}
		} finally {
			if (!paused)
				this.resume();
		}
	}

	public void saveState(OutputStream out) throws Exception {
		boolean paused = this.isPaused();
		if (!paused)
			this.pause(true);
		try {
			for (VirtualDevice device : this.getDevices()) {

				synchronized (device) {
					device.deviceSaveState(emuState);

				}

			}
			ObjectOutputStream objOut = new ObjectOutputStream(new BufferedOutputStream(out));
			//TODO 这里可能有点问题 原来是只保存getDevices() 现在emuState 会包含deviceByteState等信息
			objOut.writeObject(emuState);
			objOut.flush();
		} finally {
			if (!paused)
				this.resume();
		}
	}

	private class DeviceEntry {
		final VirtualDevice device;

		DeviceEntry(VirtualDevice device) {
			super();
			this.device = device;
		}

		void start() {
			System.out.println(device.getName() + " ready");
		}

		void init() throws Exception {
			device.deviceInit();
		}

		void exit() throws Exception {
			device.deviceExit();
		}
	}

	private class Sync extends DeviceEntry implements Runnable {
		long base_cycles;
		long base_timestamp;
		long device_freq;
		long mul_factor, div_factor;
		int max_step_cyces;
		long pauseCounter = 0;
		private volatile Thread runningThread;
		private boolean paused;

		Sync(VirtualDevice device) {
			super(device);
		}

		boolean isPaused() {
			return paused || runningThread == null;
		}

		@Override
		void start() {
			Thread t = this.runningThread = new Thread(this, device.getName());
			t.start();
			super.start();
		}

		@Override
		void exit() throws Exception {
			try {
				Thread t = this.runningThread;
				if (t != null && t.isAlive()) {
					t.interrupt();
					t.join();
				}
			} catch (InterruptedException e) {
				System.err.println(e);
			}
			super.exit();
		}

		public void run() {
			Thread t = this.runningThread = Thread.currentThread();
			String oldName = t.getName();
			t.setName(device.getName());
			try {
				long timestamp = -1;
				for (; ; ) {
					timestamp = sync_timestamp(this, timestamp);
					if (shutdown) break;
					sync(timestamp);
				}
			} catch (Throwable ex) {
				if (!(ex instanceof InterruptedException))
					ex.printStackTrace();
			}
			t.setName(oldName);
			this.runningThread = null;
		}

		void sync(long timestamp) {
			AutoDevice device = (AutoDevice) this.device;
			long next_cycles = this.next_cycles(timestamp);
			device.cycles += device.current_cycles;
			device.current_cycles = 0;
			long diffs = next_cycles - device.cycles;
			int max = (int) diffs;
			if (diffs > this.max_step_cyces) {
				this.reset_cycles(timestamp);
				max = this.max_step_cyces;            //模拟速度过慢
			}
			Inspector insp = device.getInspector();
			if (insp == null) {
				while (device.current_cycles <= max) {
					if (!device.deviceStep()) {
						//device status may changed , need reset cycles
						this.pauseCounter = -1;
						break;
					}
				}
			} else {
				while (device.current_cycles <= max) {
					if (insp.inspect()) {
						//device status changed by inspector, need reset cycles
						this.pauseCounter = -1;
						break;
					}
					if (!device.deviceStep()) {
						//device status may changed , need reset cycles
						this.pauseCounter = -1;
						break;
					}
				}
			}
		}

		void reset_cycles(long timestamp) {
			//System.err.println("reset cycles");
			AutoDevice device = (AutoDevice) this.device;
			this.device_freq = device.freq;
			this.base_cycles = device.getCycles();
			this.base_timestamp = timestamp;
			long div = max_div(NSPS, fix_freq(this.device_freq));
			this.mul_factor = (this.device_freq + div / 2) / div;
			this.div_factor = NSPS / div;
			this.max_step_cyces = 1 + (int) (this.device_freq / 10L);
			this.pauseCounter = paused_counter;
		}

		long next_cycles(long timestamp) {
			if (this.device_freq != ((AutoDevice) device).freq || this.pauseCounter != paused_counter) {
				this.reset_cycles(timestamp);
				return this.base_cycles;
			}
			return this.base_cycles + (timestamp - this.base_timestamp) * mul_factor / this.div_factor;
		}
	}

	private class Clock extends Thread {
		Clock() {
			super("Clock");
			setDaemon(true);
		}

		public void run() {
			System.out.println(this.getName() + " start");
			try {
				clockProc();
			} catch (Exception ex) {
				throw new Error("Emulator System Error", ex);
			}
		}
	}
}
